﻿using Google.Apis.Authentication;
using Google.Apis.Drive.v2;
using Google.Apis.Services;
using Ionic.Zip;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Windows.Forms;

namespace PB138___projekt
{
    /// <summary>
    /// Contains methods for getting data from and storing them back to Google Drive.
    /// </summary>
    class DataManipulator
    {
        private const string FILE_ID = "0AoTKDD6do3kVdEVtUTZ6Unpmb05ia29PM0hkdWYwZmc";
        private const string FILE_MIME_TYPE
            = "application/x-vnd.oasis.opendocument.spreadsheet";


        private Google.Apis.Drive.v2.Data.File fileToDownload;
        private string downloadedFilePath;
        private string contentFilePath;
        private string odsTempPath;


        /// <summary>
        /// Constructor for creating instances of DataManipulator.
        /// </summary>
        public DataManipulator()
        {
            contentFilePath = Path.GetTempPath();
        }

        /// <summary>
        /// Gets content of file stored on Google Drive, saves it to file and
        /// returns path to the saved file.
        /// </summary>
        /// <param name="auth">
        /// Authenticator implementing <c>IAuth</c> interface.
        /// </param>
        /// <returns>
        /// Path to the file containing content of file stored on Google Drive.
        /// Returns <c>null</c> if an error occurs.
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// If <paramref name="auth"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// If authenticator (<paramref name="auth"/>) is not valid.
        /// </exception>
        public string GetContent(IAuth auth)
        {
            if (auth == null)
            {
                throw new ArgumentNullException("auth");
            }

            if (auth.Authenticator == null)
            {
                throw new ArgumentException("Authenticator is not valid!", "auth");
            }

            // create service
            DriveService service = new DriveService(
                new BaseClientService.Initializer
                {
                    Authenticator = auth.Authenticator
                });

            try
            {
                // get file
                fileToDownload = service.Files.Get(FILE_ID).Fetch();

                Stream stream = DownloadFile(auth.Authenticator);

                if (stream == null)
                {
                    return null;
                }

                downloadedFilePath = Path.GetTempFileName();

                SaveFile(stream);

                return GetContentOfFile();
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// Downloads file from Google Drive.
        /// </summary>
        /// <param name="auth">
        /// Authenticator implementing <c>IAuthenticator</c> interface.
        /// </param>
        /// <returns>
        /// Opened stream for downloaded file. Returns <c>null</c> if an error occurs.
        /// </returns>
        private Stream DownloadFile(IAuthenticator auth)
        {
            string downloadUrl = fileToDownload.ExportLinks[FILE_MIME_TYPE];

            if (string.IsNullOrEmpty(downloadUrl))
            {
                // The file doesn't have any content stored on Drive.
                return null;
            }

            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest
                                                .Create(new Uri(downloadUrl));
                auth.ApplyAuthenticationToRequest(request);

                HttpWebResponse response = (HttpWebResponse)request.GetResponse();

                if (response.StatusCode == HttpStatusCode.OK)
                {
                    return response.GetResponseStream();
                }
                else
                {
                    return null;
                }
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// Saves file.
        /// </summary>
        /// <param name="stream">
        /// Stream for file downloaded from Google Drive.
        /// </param>
        private void SaveFile(Stream stream)
        {
            using (BinaryReader reader = new BinaryReader(stream))
            {
                byte[] array = reader.ReadBytes(100000);

                using (FileStream fs = new FileStream(downloadedFilePath,
                                                      FileMode.Create))
                {
                    while (true)
                    {
                        // Write bytes to the file.
                        fs.Write(array, 0, array.Length);

                        if (array.Length < 100000)
                        {
                            // Stream was read to its end.
                            break;
                        }
                        // Read next bytes.
                        array = reader.ReadBytes(100000);
                    }
                }
            }
        }

        /// <summary>
        /// Gets content of file downloaded from Google Drive.
        /// </summary>
        /// <returns>Path to the 'content.xml' file.</returns>
        private string GetContentOfFile()
        {
            using (ZipFile zipFile = ZipFile.Read(downloadedFilePath))
            {
                zipFile.ExtractSelectedEntries
                            (
                                "name = content.xml",
                                null,
                                contentFilePath,
                                ExtractExistingFileAction.OverwriteSilently
                            );

                contentFilePath = Path.Combine(contentFilePath, "content.xml");

                return contentFilePath;
            }
        }

        /// <summary>
        /// Stores data back to Google Drive.
        /// </summary>
        /// <param name="auth">
        /// Authenticator implementing <c>IAuth</c> interface.
        /// </param>
        /// <param name="contentPath">
        /// Path to the file containing the content of .ods file to be saved
        /// to Google Drive.
        /// </param>
        /// <param name="odsSamplePath">
        /// Path to the sample.ods file. Styles and other xml files contained
        /// in the file will be used to create .ods file with new content.
        /// </param>
        /// <returns>Success.</returns>
        /// <exception cref="ArgumentNullException">
        /// If <paramref name="auth"/>, <paramref name="contentPath"/> or
        /// <paramref name="odsSamplePath"/> is <c>null</c>.
        /// </exception>
        /// <exception cref="ArgumentException">
        /// If authenticator (<paramref name="auth"/>) is not valid or
        /// <paramref name="contentPath"/> or <paramref name="odsSamplePath"/> is
        /// empty or consists only of white-space characters.
        /// </exception>
        /// <exception cref="FileNotFoundException">
        /// If at least one of given files can not be found on
        /// <paramref name="contentPath"/> or <paramref name="odsSamplePath"/>.
        /// </exception>
        public bool StoreData(IAuth auth, string contentPath, string odsSamplePath)
        {
            if (auth == null)
            {
                throw new ArgumentNullException("auth");
            }

            if (auth.Authenticator == null)
            {
                throw new ArgumentException("Authenticator is not valid!", "auth");
            }

            if (contentPath == null)
            {
                throw new ArgumentNullException("contentPath");
            }

            if (string.IsNullOrEmpty(contentPath)
                || string.IsNullOrWhiteSpace(contentPath))
            {
                throw new ArgumentException("Content file path is empty or consists "
                    + "only of white-space characters!",
                                            "contentPath");
            }

            if (string.IsNullOrEmpty(odsSamplePath)
                || string.IsNullOrWhiteSpace(odsSamplePath))
            {
                throw new ArgumentException("Path to the .ods sample file is empty "
                    + "or consists only of white-space characters!",
                                            "odsSamplePath");
            }


            try
            {
                AddToZipFile(auth.Authenticator, contentPath, odsSamplePath);

                UploadFile(auth);
            }
            catch (FileNotFoundException)
            {
                throw;
            }
            catch (Exception)
            {
                return false;
            }

            return true;
        }

        /// <summary>
        /// Creates .ods file containing new content.
        /// </summary>
        /// <param name="auth">
        /// Authenticator implementing <c>IAuthenticator</c> interface.
        /// </param>
        /// <param name="contentPath">
        /// Path to the file containing the content of .ods file to be saved
        /// to Google Drive.
        /// </param>
        /// <param name="odsSamplePath">
        /// Path to the sample.ods file. Styles and other xml files contained
        /// in the file will be used to create .ods file with new content.
        /// </param>
        /// <exception cref="FileNotFoundException">
        /// If at least one of given files can not be found on
        /// <paramref name="contentPath"/> or <paramref name="odsSamplePath"/>.
        /// </exception>
        private void AddToZipFile(IAuthenticator auth,
                                  string contentPath,
                                  string odsSamplePath)
        {
            if (!System.IO.File.Exists(contentPath))
            {
                throw new FileNotFoundException("File '" + contentPath
                    + "' not found!");
            }

            if (!System.IO.File.Exists(odsSamplePath))
            {
                throw new FileNotFoundException("File '" + odsSamplePath
                    + "' not found!");
            }


            // Copy .ods sample file to temp directory
            odsTempPath = Path.GetTempFileName();
            System.IO.File.Copy(odsSamplePath, odsTempPath, true);

            // Add 'content.xml' to .ods file.
            using (ZipFile zipFile = ZipFile.Read(odsTempPath))
            using (FileStream fs = new FileStream(contentPath, FileMode.Open))
            {
                zipFile.UpdateEntry("content.xml", fs);
                zipFile.Save();
            }

        }

        /// <summary>
        /// Uploads .ods file to Google Drive.
        /// </summary>
        /// <param name="auth">
        /// Authenticator implementing <c>IAuth</c> interface.
        /// </param>
        private void UploadFile(IAuth auth)
        {
            // First retrieve the file from the API.
            DriveService service = new DriveService(
                new BaseClientService.Initializer
                {
                    Authenticator = auth.Authenticator
                });
            Google.Apis.Drive.v2.Data.File fileToUpload
                = service.Files.Get(FILE_ID).Fetch();

            // File's new metadata.
            fileToUpload.MimeType = FILE_MIME_TYPE;

            // File's new content.
            byte[] byteArray = System.IO.File.ReadAllBytes(odsTempPath);
            System.IO.MemoryStream stream = new System.IO.MemoryStream(byteArray);

            // Send the request to the API.
            FilesResource.UpdateMediaUpload request
                = service.Files.Update(fileToUpload, FILE_ID, stream, FILE_MIME_TYPE);
            request.NewRevision = true;
            request.Convert = true;

            try
            {
                request.Upload();
            }
            catch (Exception e)
            {
                if (auth.AuthenticatorType == AuthenticatorType
                                                    .ApplicationAuthenticator
                    && "Internal Error".Equals(e.Message))
                {
                    // There is a problem with Google Drive API. Updating of files
                    // created through UI throws WebException with internal server
                    // error when using application service account. However, the
                    // file is updated despite the fact the exception is thrown.
                }
                else
                {
                    throw;
                }
            }
            
        }

        /// <summary>
        /// Deletes all temporary files that were created.
        /// </summary>
        /// <param name="deleteContent">
        /// A value indicating whether 'content.xml' file should be deleted or not.
        /// <c>True</c> if you want to delete the file, false if not. Only file
        /// created when getting data from Google Drive will be deleted. This
        /// method does not affect 'content.xml' file passed as argument.
        /// </param>
        /// <returns>
        /// List of files that couldn't be deleted. Returns empty list if all
        /// files were successfully deleted.
        /// </returns>
        public List<string> DeleteTempFiles(bool deleteContent)
        {
            List<string> notDeletedFiles = new List<string>();

            if (System.IO.File.Exists(downloadedFilePath))
            {
                // try deleting downloaded .ods file
                try
                {
                    System.IO.File.Delete(downloadedFilePath);
                }
                catch (IOException)
                {
                    notDeletedFiles.Add(downloadedFilePath);
                }
                catch (UnauthorizedAccessException)
                {
                    notDeletedFiles.Add(downloadedFilePath);
                }
            }

            if (System.IO.File.Exists(contentFilePath) && deleteContent)
            {
                // try deleting 'content.xml'
                try
                {
                    System.IO.File.Delete(contentFilePath);
                }
                catch (IOException)
                {
                    notDeletedFiles.Add(contentFilePath);
                }
                catch (UnauthorizedAccessException)
                {
                    notDeletedFiles.Add(contentFilePath);
                }
            }

            if (System.IO.File.Exists(odsTempPath))
            {
                // try deleting newly created .ods file
                try
                {
                    System.IO.File.Delete(odsTempPath);
                }
                catch (IOException)
                {
                    notDeletedFiles.Add(odsTempPath);
                }
                catch (UnauthorizedAccessException)
                {
                    notDeletedFiles.Add(odsTempPath);
                }
            }

            return notDeletedFiles;
        }
    }
}
